#
# 2020.09.16    Tam/Alien        Added empty file check during loop of record
# 2020.09.25    Tam              Fix a issue: the bin number of last die is not added to the sum of bin number in <BOSB>
#                                add optional argument -debug and -db
#                                remove useless code

import struct
import re, os, sys, gzip
import time
import traceback
import io
import cx_Oracle
import argparse
from TOOLS import *

parser = argparse.ArgumentParser()
parser.add_argument('DAT_FIL', help='datafile name')
parser.add_argument('-debug', type=int, required=True, help='when is True, keep tmp file and print more info. default 0')
parser.add_argument('-db', required=True, help='database by which to judege HVM or LVM')
parser.add_argument('-DS_HVM_BIN_MAP',help = 'filename of HVM bin mapping', default = "HVM_Sort_Bin_Mapping.csv")
parser.add_argument('-DS_LVM_BIN_MAP',help = 'filename of LVM bin mapping', default = "Vishay_Test_Binning_Mapping.csv")
args = parser.parse_args()
#It'll generate 2 temp file, INI_TMP & SEC_TMP.  Raw binary file will be parsed and output to INI_TMP in order. INI_TMP will be reorganized and output to 
#SEC_TMP. SEC_TMP will be further reorganized and output to FIN_OUT. GLB dict stores summary info in Record0 and test result. BIN_MAP_DIR is the directory 
#where to find bin mapping file.

DAT_FIL = args.DAT_FIL
INI_TMP = DAT_FIL + '.csv.tmp1'  # tmp file
SEC_TMP = DAT_FIL + '.tmp2'      # tmp file2
PRG_FIL = DAT_FIL + '.prg'
FIN_OUT = ''
GLB = {}
BIN_MAP_DIR = os.path.join(os.getenv("DPREF"), "dat2res")

class DbNoDataError(Exception):
    pass

def main():
    global FIN_OUT
    global BIN_MAP_DIR
    ## define the length of data type in 'struct' module.
    # script will generate all these classes with corresponding data length.
    # class i with method RW, i.RW will read 4bytes and parse it as 'int' data type. 
    # I is unsigned int. l: long int. L: unsigned long int. f: float. s: string. c: char. b: byte. h: short int. H: unsigned short int.
    type_table = ['s', 'c', 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'e', 'f', 'd']
    len_table =  [1,    1,   1,   1,   2,   2,   4,   4,   4,   4,   8,   8,   2,   4,   8]
    
    ## parse datafile filename
    isGzFile = re.search(r'^(.*).(gz|GZ)$', DAT_FIL)
    if not isGzFile:
        BASE_NAME = os.path.basename(DAT_FIL)
    else:
        BASE_NAME = os.path.basename(isGzFile.group(1))
    DIR_NAM = os.path.dirname(DAT_FIL)
    
    if (BASE_NAME.find('_') != -1):
        M = re.findall(r'([^_]*)(_|__)([^_]*).(dat|DAT)', BASE_NAME)
        LOT = M[0][0]
        TIME = M[0][2]
    else:
        M = re.findall(r'([^_]*).(dat|DAT)', BASE_NAME)
        LOT = M[0][0]
        TIME = time.strftime("%Y%m%d%H%M%S", time.localtime())
    if not M:
        raise Exception('inrecognized filename')

    LOT_ID1 = LOT[0:-3]
    LOT_ID2 = LOT[-3:-1]
    WAFER_NUM = LOT[-1:]
    LOT_ID = LOT_ID1 + str(int(LOT_ID2, 36)).rjust(3, '0')
    WAFER_NUM = int(WAFER_NUM, 36)
    WAFER_ID = LOT_ID + '_' + str(WAFER_NUM).rjust(2, '0')
    WAFER_ID_4SQL = LOT_ID + '.' + str(WAFER_NUM)
    WAFER_ID_4SQL = WAFER_ID_4SQL.upper()
    #    WAFER_ID_4SQL = "EJ48D03.10"
    FIN_OUT = os.path.join(DIR_NAM, WAFER_ID) + '__' + TIME + '.res'  # output fileName

    ## file type HVM or LVM is judged by GEOM_NAME that retrieved from db.
    if not args.debug:
        dsn_tns = cx_Oracle.makedsn('db-01', '1521', service_name='vexndb')
        #conn = cx_Oracle.connect(user=r'exn_dba', password=r'xN$abd847', dsn=dsn_tns)
        conn = cx_Oracle.connect(user=r'', password=r'', dsn=dsn_tns)
        with conn.cursor() as cur:
            cur.execute('alter session set current_schema = ' + args.db)
            sql_mosds = "select geom_name from promis_mos_ds where component_id = :COMPONENT"
            cur.execute(sql_mosds, {"COMPONENT": WAFER_ID_4SQL})
            GEOM_NAME_result = cur.fetchall()

        if len(GEOM_NAME_result) == 0:
            raise DbNoDataError("cannot find geom_name from promis_mos_ds where component_id ={} ".format(WAFER_ID_4SQL))
        if (not GEOM_NAME_result[0][0].strip()) or re.search(r'na', GEOM_NAME_result[0][0].strip(), re.I) or len(GEOM_NAME_result) == 0:
            raise Exception('4can not get GEOM_NAME from db promis_mos_ds')
        g_name = GEOM_NAME_result[0][0]
    else:
        g_name = 'H'
    if g_name.startswith('H') or g_name.startswith('AH'):
        GLB['HVM_LVM_FLAG'] = 'HVM'
    else:
        GLB['HVM_LVM_FLAG'] = 'LVM'
    ##
    ##bin mapping file is get from argument.
    if GLB['HVM_LVM_FLAG'] == 'HVM':
        BIN_MAP = os.path.join(BIN_MAP_DIR, args.DS_HVM_BIN_MAP)
    else:
        BIN_MAP = os.path.join(BIN_MAP_DIR, args.DS_LVM_BIN_MAP)
    ##
    #BIN MAP func
    #some functions used to search in bin mapping file.
    bin_mapping_list = []
    with open(BIN_MAP) as BIN:
        lineNum = 0
        for line in BIN:
            line = line.strip()
            if lineNum != 0:
                bin_mapping_list.append(line.split(','))
            lineNum += 1
        if GLB['HVM_LVM_FLAG'] == 'LVM':
            bin_mapping_list = [[row[2], row[0], row[1], row[3]] for row in bin_mapping_list]

    def find_name_by_bin(bin_num):
        if GLB['wafer_map_exist']:
            if bin_num == 1:
                return 'PASS'
        else:
            if bin_num in (1, 2):
                return 'PASS'
        for i in bin_mapping_list:
           if int(i[0]) == bin_num:
                return i[3]
        raise Exception(f"can not find name by bin number {bin_num}")

    def find_name_by_test_num(test_num): #test name
        for i in bin_mapping_list:
            if i[2] and int(i[2]) == test_num:
                return i[3]
        raise Exception(f"can not find name by test number {test_num}")

    def find_bin_by_test_num(test_num):  #bin number
        for i in bin_mapping_list:
            if i[2] and test_num == int(i[2]):
                return int(i[0])
        raise Exception(f"can not find bin number by test number {test_num}\n")

    #func
    def output(name, unpacked, data, d_binary=False, bin_len=1):
        data1 = data.hex()
        data2 = bin(int(data1, 16))[2:].rjust(8 * bin_len, "0")
        if d_binary:
            unpacked = data2
        W.write(f'{name},{unpacked},{data1},{data2}\n')
    #convert byte string b'XX' to int 
    def b2int(data):
        return int(data.hex(), 16)

    # def type class
    ## CreateBasicClass will generate all classes defined in 'type_table'
    # RW method will READ data with size self.length and WRITE parsed data to tmp1 file.
    def CreateBasicClass(tp_name):
        class Basic:
            def __init__(self, name):
                self.name = name
                self.tp_name = tp_name
                indx = type_table.index(tp_name)
                self.length = len_table[indx]
            def RW(self):
                data = R.read(self.length)
                if self.tp_name not in ('c'):
                    unpacked = struct.unpack(self.tp_name, data)[0]
                elif self.tp_name == 'c':
                    unpacked = struct.unpack(self.tp_name, data)[0].decode()
                output(self.name, unpacked, data)
                return unpacked
        return Basic

    for i, j in enumerate(type_table):
        globals()[j] = CreateBasicClass(j)
        
    #class TP_SKIP is used to simply skip empty data.
    class TP_SKIP:
        def __init__(self, d_length):
            self.tp_name = 'TP_SKIP'
            self.length = d_length
        def RW(self):
            R.read(self.length)
    #class TP_STRING parse string type data. It can deal with data named DATE.
    class TP_STRING:
        def __init__(self, name, d_length):
            self.name = name
            self.tp_name = 'TP_STRING'
            self.length = d_length
        def RW(self):
            data = R.read(1)
            if  self.name == 'DATE': #use timestamp in filename as DATE
                N = re.search(r'^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d*)$', TIME)
                now = f'{N.group(1)}-{N.group(2)}-{N.group(3)} {N.group(4)}:{N.group(5)}:{N.group(6)}'
                GLB['time'] = now
                output(self.name, now, b'\x00')
                R.read(self.length - 1)
            elif data.hex() == '00':
                output(self.name,'nan',b'\x00')
                R.read(self.length - 1)
            else:
                length_record = struct.unpack('B', data)[0]
                data2 = R.read(length_record)
                unpacked = struct.unpack(f'{length_record}s', data2)[0].decode()
                output(self.name, unpacked, data)
                R.read(self.length - 1 - length_record)
                return unpacked

    # self-defined type
    ## For below classes, pls refer to 'DS_FET_HVM (PARSER SPEC).docx'
    #e.g. There is field name DFG1 in the .docx, so creating a corresponding class TP_DFG1.
    #instance = TP_DFG1('a_name') instance.RW() will read 1 byte and parse it as the rule defines in .docx.
    #important classes: TP_DFG1, TP_SN_OR_WM, TP_RSLTF.
    #TP_DFG1:   wafer_map_exist = True or False
    #           long_SN_enabled = True or False(Series number = 2 if False else 4)
    #          'Series number' is in docx page15. 
    #TP_SN_OR_WM:
    #           SN_NUMBER(SN)
    #TP_RSLTF:  PF(pass or fail)
    class TP_DFG1:
        def __init__(self, name):
            self.name = name
            self.tp_name = 'TP_DFG1'
            self.length = 1
        def RW(self):
            data = R.read(1)
            data_int = b2int(data)
            bit0 = data_int & 0b00000001
            bit1 = (data_int & 0b00000010) >> 1
            bit2 = (data_int & 0b00000100) >> 2
            GLB['wafer_map_exist'] = True if bit0 else False
            GLB['convert2V12'] = True if bit1 else False
            GLB['long_SN_enabled'] = True if bit2 else False
            W.write(f'{self.name}\n')
            output(f'wafer_map_exist', GLB['wafer_map_exist'], data)
            output(f'convert2V12', GLB['convert2V12'], data)
            output(f'long_SN_enabled', GLB['long_SN_enabled'], data)

    class TP_BCD:
        def __init__(self, name, length):
            self.name = name
            self.tp_name   = 'TP_BCD'
            self.length = length
        def RW(self):
            total = ''
            for i in range(self.length):
                data = R.read(1)
                num = b2int(data)
                num1 = (num & 0b11110000)>>3
                num2 = num & 0b00001111
                total = total + str(num1) + str(num2)
            output(self.name, total, data)
            return total

    class TP_BLOS:
        def __init__(self, name):
            self.name = name
            self.tp_name   = 'TP_BLOS'
            self.length = 1
        def RW(self):
            data = R.read(1)
            data_int = b2int(data)
            bit0 = data_int & 0b00000001
            bit1 = (data_int & 0b00000010) >> 1
            GLB['BIN_OPEN_CLOSE'] = 'OPEN' if bit0 == 0 else 'CLOSE'
            GLB['BIN_REJECT'] = True if bit1 == 1 else False
            W.write(f'{self.name}\n')
            output(f'BIN_OPEN_CLOSE', GLB['BIN_OPEN_CLOSE'], data)
            output(f'BIN_REJECT', GLB['BIN_REJECT'], data)

    class TP_SN_OR_WMP:
        def __init__(self, name):
            self.name = name
            self.tp_name = 'TP_SN_OR_WMP'
            self.length = None
    
        def RW(self, SN=[0]):
            SN[0] += 1
            W.write(f'SN,{SN[0]}\n')
            W.write(f'{self.name}\n')
            self.length = 4 if GLB['long_SN_enabled'] else 2
            if GLB['wafer_map_exist'] and not GLB['long_SN_enabled']:
                B('xcoord').RW()
                B('ycoord').RW()
            elif not GLB['wafer_map_exist'] and not GLB['long_SN_enabled']:
                H('SN_NUMBER').RW()
            elif not GLB['wafer_map_exist'] and GLB['long_SN_enabled']:
                L('SN_NUMBER').RW() ##if long_SN_enabled, THEN length of SN_NUMBER = 4 bytes. else, 2 bytes.
            else:
                raise Exception('Error, Serial Number & wafer mapping')

    class TP_BIN_RESULT:
        def __init__(self, name):
            self.name = name
            self.tp_name = 'TP_BIN_RESULT'
            self.length = 1
        def RW(self):
            data = R.read(1)
            bit0_4 = b2int(data) & 0b00011111
            bit7 = (b2int(data) & 0b10000000) >> 7
            W.write(f'{self.name}\n')
            if 'hardBin' not in GLB.keys():
                GLB['hardBin'] = []
            GLB['hardBin'].append(bit0_4)
    
            tmp = True if bit7 else False
            output(f'reject', tmp, data)

    class TP_RSLTF:
        def __init__(self, name):
            self.name = name
            self.tp_name = 'TP_RSLTF'
            self.length = 1
        def RW(self):
            data = R.read(1)
            data_int = b2int(data)
            bit3 = (data_int & 0b00001000)>>3
            self.PF = 'F' if bit3 else 'P'
            output('PF',self.PF, data)
            return self.PF

    name1   = c('name1')
    name2   = c('name2')
    SSN     = H('SSN')
    ESN     = H('ESN')
    SNNUM   = B('SNNUM')
    SNSIZE  = H('SNSIZE')
    TEST    = B('TEST') #32
    FCN     = B('FCN') #32
    REM     = TP_STRING('REM', 40)
    DATE    = TP_STRING('DATE', 40)  #set script runtime as date/time
    START_SEG   = B('START_SEG')
    END_SEG = B('END_SEG')
    DFG1    = TP_DFG1('DFG1')
    SCX     = B('SCX')
    SCY     = B('SCY')
    ECX     = B('ECX')
    ECY     = B('ECY')
    SPARE   = TP_SKIP(4)
    run_filename    = TP_STRING('run_filename', 15)
    test_filename   = TP_STRING('test_filename', 15)
    BCNT    = TP_BCD('BCNT', 4) #25
    BLOS    = TP_BLOS('BLOS') #25
    RSCNT   = TP_BCD('RSCNT', 4) #25
    RTCNT   = TP_BCD('RTCNT', 4)
    LSSN    = L('LSSN')
    LESN    = L('LESN')
    SN_OR_WMP       = TP_SN_OR_WMP('SN_OR_WMP')
    bin_result_bits = TP_BIN_RESULT('bin_result_bits')
    RSLTF = TP_RSLTF('RSLTF')
    RES = f('RES')
    
    if not isGzFile:
        R = open(DAT_FIL, 'rb')
    else:
        R = gzip.open(DAT_FIL, 'rb')
    R = io.BytesIO(R.read())

    #check file size
    GLB['FIL_LEN'] = len(R.read())
    R.seek(0, 0)
    assert GLB['FIL_LEN'] % 1536 == 0, f"incomplete file"
    assert GLB['FIL_LEN'] != 0, f"Empty File"

    W = open(INI_TMP, 'w')
    ## parse record0
    section0 = [name1, name2, SSN, ESN, SNNUM, SNSIZE, TEST, FCN, REM, DATE, START_SEG, END_SEG, DFG1, SCX, SCY, ECX, \
                ECY, SPARE, run_filename, test_filename, 'BCNT_BLOS', RSCNT, RTCNT]
    for i in section0:
        if i == TEST:
            GLB['TEST'] = []
            for j in range(32):
                test_number = i.RW()
                if test_number != 0:
                    GLB['TEST'].append(test_number)
        elif i == FCN:
            GLB['FCN'] = []
            for j in range(32):
                FCN_number = i.RW()
                GLB['FCN'].append(FCN_number)
        elif i == 'BCNT_BLOS':
            for j in range(25):
                BCNT.RW()
                BLOS.RW()
        elif i == RSCNT:
            for j in range(25):
                i.RW()
        elif i in [SSN, ESN, SNNUM, SNSIZE, REM, DATE, START_SEG, END_SEG, SCX, \
                   SCY, ECX, ECY, run_filename, test_filename]:
            GLB[i.name] = i.RW()
        else:
            i.RW()
    if GLB['long_SN_enabled']:
        LSSN.RW()
        LESN.RW()
    ## skip current die. Goto next die. Because the tail of each die(SN) maybe has empty area with uncertain size.
    # This func should be provided SN_start(the location of the start of current die. e.g. It starts 1537 bytes.).
    # die_cnt, the SN_NUMBER(die index) in current record. such as 2nd die in current record.
    def next_SN(SN_start, die_cnt):
        if die_cnt % GLB['SNNUM'] != 0:
            # next die is in next record. In another word, current die is the last one in current record.
            R.seek(SN_start + GLB['SNSIZE'], 0)  # next die
        else:
            tmp = 1536 - GLB['SNSIZE'] * GLB['SNNUM']
            R.seek(SN_start + GLB['SNSIZE'] + tmp, 0)  # next record
    
    def atEndOfFile():
        data = R.read(1)
        if data == b'':
            return 1
        else:
            R.seek(-1,1)
            return 0

    SN_LENGTH = 4 if GLB['long_SN_enabled'] else 2
    
    def isEmptySN():
        data = R.read(SN_LENGTH)
        R.seek(-1 * SN_LENGTH, 1)
        if b2int(data) == 0:
            return 1
        else:
            return 0

    ##parse record 1...N
    record_cnt = 0
    SN_LENGTH = 4 if GLB['long_SN_enabled'] else 2
    R.seek(1536, 0)
    while 1:#record
        die_cnt = 1
        record_cnt += 1
        
        if atEndOfFile():
            break

        ## check some data get from record0 is empty or not. If it's empty, it suggests raw data file has error.
        if GLB['SNNUM'] == 0 or GLB['SNSIZE'] == 0 \
            or (GLB['START_SEG'] == GLB['END_SEG'] == 0) \
            or ('run_filename' not in GLB.keys() or GLB['run_filename'] is None) \
            or ('test_filename' not in GLB.keys() or GLB['test_filename'] is None) \
            or ('long_SN_enabled' not in GLB.keys() or GLB['long_SN_enabled'] is None):
            raise Exception("Empty file")
        ##
        ## a record has GLB['SNNUM'] Dies. Each Die size = GLB['SNSIZE']
        # Each Die has (END_SEG - START_SEG + 1) SEGs.
        # Each SEG has TESTs, stores in  GLB['TEST']. e.g. GLB['TEST'] = [5,6,8,3,2,1]
        # It means test number of first TEST is 5, 2nd TEST is 6. It's got from record0. parsed result stores in INIT_TMP.
         
        W.write(f'record,{record_cnt}\n')
        while 1:  # SN/die
            die_cnt += 1
            test_number = 0
            SN_start = R.tell()
            #if is the last die, goto beginning of next record
            if die_cnt -1  > GLB['SNNUM']:
                next_SN(last_SN_start, die_cnt - 2 )
                break
            else:
                last_SN_start = SN_start

            if atEndOfFile():
                break
            if isEmptySN():
                next_SN(SN_start, die_cnt - 1 )
                continue

            SN_OR_WMP.RW()
            bin_result_bits.RW()
            seg_count = GLB['END_SEG'] - GLB['START_SEG'] + 1
            
            ##seg  test     res
            # In TMP_FIL:
            # 0     0        XX
            # 1     0        0(empty)
            # 0     1        0
            # 1     1        XX
            # 0     2        XX
            # 1     2        0
            # 0     3        0
            # 1     3        XX ...
            for i in range(len(GLB['TEST'])):
                for j in range(seg_count):
                    print(f"seg,{j}",file=W)
                    print(f"test_seq,{i}",file=W) #start from 0 
                    RSLTF.RW()
                    RES.RW()
            next_SN(SN_start, die_cnt-1)
            
    R.close()
    W.close()
    # reformat output file
    ## data order of INIT_TMP is the same with raw data, reorganize INIT_TMP to SEC_TMP.
    with open(SEC_TMP, 'w') as W:
        W.write('<BOH>\n')
        W.write(f'StartTime,{GLB["time"]}\n')
        W.write('StartTimeFmt,%Y-%m-%d %H:%M:%S\n')
        W.write(f'EndTime,{GLB["time"]}\n')
        W.write('EndTimeFmt,%Y-%m-%d %H:%M:%S\n')
        W.write('<EOH>\n\n')
        
        W.write('<BOR>\n')
        W.write('#Test_name,test_num,low_limit,high_limit\n')
        for i in GLB['TEST']:
            W.write(f'{find_name_by_test_num(i)},{i}\n')
        
        W.write('program,LOT_ID,WAFER_ID,TesterType,failedTestNumber,SN,dieX,dieY,softBinNum,softBinName,RES')
        indx1 = GLB['test_filename'].find('.')
        GLB['program'] = GLB['test_filename'][0:indx1]
        head_string = '\n' + GLB['program'] + ',' + LOT_ID + ',' + WAFER_ID + ',' + 'FetTest' + ','
        
        with open(INI_TMP, 'r') as F:
        # to understand this part code, pls open INIT_TMP file.
        # all res of current SN is stores in RES_tmp
        # failed_test_num is the test number of last test.
            RES_tmp = []
            RES_PER_SEG_TMP = {}
            UNION_RES_TMP   = {}
            while 1:
                line = F.readline()
                splited = line.strip().split(',')
                if line == '' or (len(splited) >= 2 and splited[0] == 'SN'):
                       
                    if line == '' or int(splited[1]) > 1: #if SN == 2, then we deal with the die with SN==1
                        ##if splited[0] == 'SN', it means a start of die.
                        ##if splited[0] == 'SN', then we deal res of last die
                        for SEG_TMP in RES_PER_SEG_TMP.keys():
                            UNION_RES_TMP.update(RES_PER_SEG_TMP[SEG_TMP])
                        # if a TEST is not executed, set the test res = ''
                        for i in range(len(GLB['TEST'])):
                            if i not in UNION_RES_TMP.keys():
                                UNION_RES_TMP[i] = 'NA'
                        # remove trailing ''
                        for i in reversed(range(len(GLB['TEST']))):
                            if UNION_RES_TMP[i] != 'NA':
                                break
                            else:
                                del UNION_RES_TMP[i]
                        RES_tmp = []
                        for i in sorted(UNION_RES_TMP.keys()):
                            RES_tmp.append(UNION_RES_TMP[i])
                     
                        if 'last_PF_tmp' in dir() and last_PF_tmp == 'P':
                            bin_num_tmp = 1
                            bin_name_tmp = 'PASS'
                            failed_test_num_tmp = 0
                        elif 'last_PF_tmp' in dir() and last_PF_tmp == 'F':
                            test_num = GLB['TEST'][len(RES_tmp) - 1]
                            bin_num_tmp = find_bin_by_test_num(test_num)
                            bin_name_tmp = find_name_by_test_num(test_num)
                            failed_test_num_tmp = test_num
                        else:
                            bin_num_tmp  =''
                            bin_name_tmp = ''
                            failed_test_num_tmp = ''
                            test_num = ''
                        
                        W.write(head_string)
                        if 'diex_tmp' not in dir():
                            diex_tmp = ''
                        if 'diey_tmp' not in dir():
                            diey_tmp = ''
                        W.write(
                            f'{failed_test_num_tmp},{part_id_tmp},{diex_tmp},{diey_tmp},{bin_num_tmp},{bin_name_tmp},')
                        part_id_tmp += 1

                        if 'softBinNum' not in GLB.keys():
                            GLB['softBinNum'] = []
                        if 'softBinName' not in GLB.keys():
                            GLB['softBinName'] = []
                        GLB['softBinNum'].append(bin_num_tmp)
                        GLB['softBinName'].append(bin_name_tmp)
                        
                        for i in RES_tmp:
                            W.write(f'{i},')
                        if line == '': # end of file
                            break
                    else:##This is for case SN==1. Part_id_tmp is a alias of SN
                        part_id_tmp = int(splited[1])        
                    #initial tmp variable for next die
                    RES_tmp = []
                    RES_PER_SEG_TMP = {}
                    UNION_RES_TMP   = {}
                elif len(splited) >= 2 and splited[0] == 'xcoord':  # 1
                    diex_tmp = splited[1]
                elif len(splited) >= 2 and splited[0] == 'ycoord':  # 2
                    diey_tmp = splited[1]
                elif len(splited) >= 2 and splited[0] == 'seg':
                    seg_tmp = int(splited[1])
                    if seg_tmp not in RES_PER_SEG_TMP.keys():
                        RES_PER_SEG_TMP[seg_tmp] = {}
                elif len(splited) >= 2 and splited[0] == 'test_seq':
                    test_seq_tmp = int(splited[1])
                elif len(splited) >= 2 and splited[0] == 'RES':  # 3
                    if splited[1] != '0.0':
                        last_PF_tmp = 'P' if this_tests_PF == 'P' else 'F'
                        if test_seq_tmp not in RES_PER_SEG_TMP.keys():
                            RES_PER_SEG_TMP[seg_tmp][test_seq_tmp] = {}
                        RES_PER_SEG_TMP[seg_tmp][test_seq_tmp] = splited[1]
                elif len(splited) >= 2 and splited[0] == 'PF':
                    this_tests_PF = splited[1]
        W.write('\n<EOR>\n\n')
        
        W.write('<BOSB>\n')
        W.write('binNumber,binName,PF,count\n')
        # GLB['softBinNum'] stores the failed bin num of each SN(die).
        if 'softBinNum' in GLB.keys():
            for i in set(GLB['softBinNum']):
                soft_bin_name_tmp = find_name_by_bin(int(i))
                PF_tmp = 'P' if soft_bin_name_tmp == 'PASS' else 'F'
                W.write(f'{i},{soft_bin_name_tmp},{PF_tmp},{GLB["softBinNum"].count(i)}\n')
        W.write('<EOSB>\n\n')
    
    with open(FIN_OUT, 'w') as W2:
        with open(SEC_TMP, 'r') as F:
            flag = ''
            for line in F:
                if re.findall(r'^<BOH>', line):
                    flag = 'BOH'
                elif re.findall(r'^<BOSB>', line):
                    flag = 'BOSB'
                elif re.findall(r'^<BOR>', line):
                    flag = 'BOR'
                
                if flag == 'BOH':
                    W2.write(line)
                if flag == 'BOSB':
                    W2.write(line)
        with open(SEC_TMP, 'r') as F:
            for line in F:
                if re.findall(r'^<BOR>', line):
                    flag = 'BOR'
                elif re.findall(r'^<BOSB>', line):
                    flag = 'BOSB'
                
                if flag == 'BOR':
                    W2.write(line)
    #some values should be passed to other scripts.
    with open(PRG_FIL, 'w') as W:
        print(GLB['program'], file=W)
        print(FIN_OUT, file=W)
        print(BIN_MAP, file=W)

def clean_tmp():
    rm_file(FIN_OUT)
    rm_file(PRG_FIL)

#In debug mode, print traceback, keep temp file. Otherwise delete them.
if __name__ == '__main__':
    try:
        main()
    except DbNoDataError as ins:
        clean_tmp()
        exit_for_loader(10, repr(ins))
    except BaseException as ins:
        if not args.debug:
            clean_tmp()
            if str(ins)[0]=='4':
                exit_for_loader(4,repr(ins))
            else:
                exit_for_loader(1, repr(ins))
        else:
            print(traceback.format_exc())
            exit_for_loader(1, repr(ins))
    finally:
        if not args.debug:
            for file in (INI_TMP, SEC_TMP):
                rm_file(file)
